package biz.datalk.industrialland.common.log.util;

import biz.datalk.industrialland.common.encrypt.sm.GMUtils;
import biz.datalk.industrialland.common.encrypt.sm.SM4Util;
import biz.datalk.industrialland.common.exception.ServiceException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

/**
 * oauth2 工具类
 *
 * @author tarofang@163.com
 * @date 2020-09-09
 */
public class LogOauth2Util {
    private static final Logger log = LoggerFactory.getLogger(LogOauth2Util.class);
    // private static final byte[] AES_KEY= {0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F};
    private static final byte[] SM4_KEY= {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,0x00};
    private static final String ITEM_SEPARATOR = "|";
    private static final String TOKEN_SEPARATOR = "_";


    public static String generateAccessToken(String appkey, LocalDateTime createTime, LocalDateTime expireTime) {
        // String encodeAppkey = AESTool.encrypt(appkey, AES_KEY);
        String encodeAppkey = GMUtils.sm4Encrypt(appkey, Hex.toHexString(SM4_KEY));

        long timestamp = createTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
        long expireTimestamp = expireTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();

        String preAccessToken = encodeAppkey + ITEM_SEPARATOR + timestamp + ITEM_SEPARATOR + expireTimestamp;

        // byte[] bytes = AESTool.encryptByte(preAccessToken, AES_KEY);
        byte[] bytes;
        try {
            bytes = SM4Util.encryptECBPadding(SM4_KEY, preAccessToken.getBytes(StandardCharsets.UTF_8));
        } catch (Exception ex) {
            throw new ServiceException(ex);
        }

        int len = bytes.length;
        int changeLength = (int) (Math.random() * (len + 1 - 1) + 1);
        byte[] mvBytes = new byte[len];
        System.arraycopy(bytes, changeLength, mvBytes, 0, len - changeLength);
        System.arraycopy(bytes, 0, mvBytes, len - changeLength, changeLength);

        String accessToken = Base64.encodeBase64URLSafeString(mvBytes);
        accessToken = changeLength + TOKEN_SEPARATOR + accessToken;

        return accessToken;
    }

    public static String getAppkeyFromAccessToken(String accessToken) {
        try {
            // String[] split = StringUtils.split(accessToken, TOKEN_SEPARATOR, 2);
            // 39__WYJ434fCw_Cj8hIRKp37a5Oq5F2gHhCuehkQayfGLn6o0YVLLJ1MDhOcmBj3CvqCvCEOZxXPrQcIk9QQvDxfQ
            // String[] split = StringUtils.split(accessToken, TOKEN_SEPARATOR, 2);
            String[] split = StringUtils.splitByWholeSeparatorPreserveAllTokens(accessToken, TOKEN_SEPARATOR, 2);
            if (split == null || split.length != 2) {
                log.debug("Invalid accessToken: {}", accessToken);
                return null;
            }
            int changeLength  = Integer.parseInt(split[0]);
            String token = split[1];

            byte[] bytes = Base64.decodeBase64(token);
            int len = bytes.length;
            byte[] mvBytes = new byte[len];

            System.arraycopy(bytes, 0, mvBytes, changeLength, len - changeLength);
            System.arraycopy(bytes, len - changeLength, mvBytes, 0, changeLength);
            // String preAccessToken = AESTool.decryptByte(mvBytes, AES_KEY);
            String preAccessToken = GMUtils.sm4Decrypt(Hex.toHexString(mvBytes), Hex.toHexString(SM4_KEY));

            String[] preAccessTokenSplit = StringUtils.split(preAccessToken, ITEM_SEPARATOR);
            String appkey = preAccessTokenSplit[0];
            // appkey = AESTool.decrypt(appkey, AES_KEY);
            appkey = GMUtils.sm4Decrypt(appkey, Hex.toHexString(SM4_KEY));
            return appkey;
        } catch (Exception ex) {
            log.warn("Invalid accessToken: {}, ERR: {}", accessToken, ex.getMessage(), ex);
            return null;
        }
    }

    public static AccessToken parseAccessToken(String accessToken) {
        try {
            String[] split = StringUtils.splitByWholeSeparatorPreserveAllTokens(accessToken, TOKEN_SEPARATOR, 2);
            if (split == null || split.length != 2) {
                log.debug("Invalid accessToken: {}", accessToken);
                return null;
            }
            int changeLength  = Integer.parseInt(split[0]);
            String token = split[1];

            byte[] bytes = Base64.decodeBase64(token);
            int len = bytes.length;
            byte[] mvBytes = new byte[len];

            System.arraycopy(bytes, 0, mvBytes, changeLength, len - changeLength);
            System.arraycopy(bytes, len - changeLength, mvBytes, 0, changeLength);
            // String preAccessToken = AESTool.decryptByte(mvBytes, AES_KEY);
            String preAccessToken = GMUtils.sm4Decrypt(Hex.toHexString(mvBytes), Hex.toHexString(SM4_KEY));

            String[] preAccessTokenSplit = StringUtils.split(preAccessToken, ITEM_SEPARATOR);
            String appkey = preAccessTokenSplit[0];
            String createTimestampStr = preAccessTokenSplit[1];
            String expireTimestampStr = preAccessTokenSplit[2];

            long createTimestamp = Long.parseLong(createTimestampStr);
            long expireTimestamp = Long.parseLong(expireTimestampStr);
            LocalDateTime create = LocalDateTime.ofInstant(Instant.ofEpochMilli(createTimestamp), ZoneId.systemDefault());
            LocalDateTime expire = LocalDateTime.ofInstant(Instant.ofEpochMilli(expireTimestamp), ZoneId.systemDefault());

            // appkey = AESTool.decrypt(appkey, AES_KEY);
            appkey = GMUtils.sm4Decrypt(appkey, Hex.toHexString(SM4_KEY));

            AccessToken resultAT = new AccessToken();
            resultAT.setAppkey(appkey);
            resultAT.setCreateTimestamp(createTimestamp);
            resultAT.setExpireTimestamp(expireTimestamp);
            resultAT.setCreate(create);
            resultAT.setExpire(expire);
            return resultAT;
        } catch (Exception ex) {
            log.warn("Invalid accessToken: {}, ERR: {}", accessToken, ex.getMessage(), ex);
            return null;
        }
    }


    public static class AccessToken implements Serializable {
        private static final long serialVersionUID = 1L;

        private String appkey;
        private long createTimestamp;
        private long expireTimestamp;

        private LocalDateTime create;
        private LocalDateTime expire;

        /** 是否过期 true-过期 false-未过期 */
        public boolean isExpire() {
            return System.currentTimeMillis() > this.expireTimestamp;
        }

        public String getAppkey() {
            return appkey != null ? appkey.trim() : null;
        }

        public void setAppkey(String appkey) {
            this.appkey = appkey;
        }

        public long getCreateTimestamp() {
            return createTimestamp;
        }

        public void setCreateTimestamp(long createTimestamp) {
            this.createTimestamp = createTimestamp;
        }

        public long getExpireTimestamp() {
            return expireTimestamp;
        }

        public void setExpireTimestamp(long expireTimestamp) {
            this.expireTimestamp = expireTimestamp;
        }

        public LocalDateTime getCreate() {
            return create;
        }

        public void setCreate(LocalDateTime create) {
            this.create = create;
        }

        public LocalDateTime getExpire() {
            return expire;
        }

        public void setExpire(LocalDateTime expire) {
            this.expire = expire;
        }

        @Override
        public String toString() {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            return "AccessToken{" + "appkey='" + appkey + '\'' + ", createTimestamp=" + createTimestamp + ", expireTimestamp=" + expireTimestamp + ", create=" + create.format(formatter) + ", expire=" + expire.format(formatter) + '}';
        }
    }

    public static void main(String[] args) {
        String appkey = "100111";
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime expire = now.plus(2, ChronoUnit.HOURS);
        expire = expire.plus(10, ChronoUnit.SECONDS);
        String accessToken = LogOauth2Util.generateAccessToken(appkey, now, expire);
        System.out.println(accessToken);
    }

}
